我们目前还缺少消息的集中定义。在大型软件(如编译器)中，你应该不会希望消息字符串散布各处。若有修改消息或将其翻译成另一种语言的请求，最好将它们放在一个中心位置!一种简单的方法是，每个消息都有一个ID(枚举成员)、一个严重级别(如Error或Warning)和一个包含消息的字符串。在代码中，只需要引用消息ID即可。

严重性级别和消息字符串仅在消息输出时使用，必须一致地管理这三项(ID、安全级别和消息)。LLVM库使用预处理器来解决这个问题，数据存储在一个后缀为.def的文件中，并包装在一个宏名称中。该文件通常被包含多次，并对该宏有不同的定义。该定义位于include/tinylang/Basic/Diagnostic.def文件路径中:

\begin{cpp}
#ifndef DIAG
#define DIAG(ID, Level, Msg)
#endif

DIAG(err_sym_declared, Error, "symbol {0} already declared")

#undef DIAG
\end{cpp}

第一个宏参数ID是枚举标签，第二个参数Level是严重性，第三个参数Msg是消息文本。有了这个定义，就可以定义一个diagnosticengine类来发出错误消息。接口在include/tinylang/ Basic/Diagnostic.h文件中:

\begin{cpp}
#ifndef TINYLANG_BASIC_DIAGNOSTIC_H
#define TINYLANG_BASIC_DIAGNOSTIC_H

#include "tinylang/Basic/LLVM.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/SMLoc.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include <utility>

namespace tinylang {
\end{cpp}

在包含必要的头文件之后，可以使用Diagnostic.def定义枚举。为了不污染全局命名空间，我们使用了名为diag的嵌套命名空间:

\begin{cpp}
namespace diag {
enum {
#define DIAG(ID, Level, Msg) ID,
#include "tinylang/Basic/Diagnostic.def"
};
} // namespace diag
\end{cpp}

DiagnosticsEngine类使用SourceMgr实例通过report()方法发送消息。消息可以有参数。为了实现这个功能，我们使用了LLVM提供的可变格式支持。消息文本和严重性级别在静态方法的帮助下检索，还会统计发出的错误消息的数量:

\begin{cpp}
class DiagnosticsEngine {
    static const char *getDiagnosticText(unsigned DiagID);
    static SourceMgr::DiagKind
    getDiagnosticKind(unsigned DiagID);
\end{cpp}

消息字符串由getDiagnosticText()返回，而级别由getDiagnosticKind()返回。这两个方法稍后会在.cpp文件中进行实现:

\begin{cpp}
SourceMgr &SrcMgr;
unsigned NumErrors;

public:
    DiagnosticsEngine(SourceMgr &SrcMgr)
        : SrcMgr(SrcMgr), NumErrors(0) {}
    unsigned nunErrors() { return NumErrors; }
\end{cpp}

由于消息可以具有可变数量的参数，所以C++中的解决方案是使用可变模板。当然，LLVM提供的formatv()函数也会使用这种方法。要获得格式化的消息，只需要转发模板参数即可:

\begin{cpp}
    template <typename... Args>
    void report(SMLoc Loc, unsigned DiagID,
                Args &&... Arguments) {
        std::string Msg =
            llvm::formatv(getDiagnosticText(DiagID),
        std::forward<Args>(Arguments)...)
        .str();
        SourceMgr::DiagKind Kind = getDiagnosticKind(DiagID);
        SrcMgr.PrintMessage(Loc, Kind, Msg);
        NumErrors += (Kind == SourceMgr::DK_Error);
    }
};

} // namespace tinylang
#endif
\end{cpp}

至此，我们已经实现了类的大部分内容，只缺失了getDiagnosticText()和getDiagnosticKind()。它们在lib/Basic/Diagnostic.cpp文件中定义，并使用了Diagnostic.def文件:

\begin{cpp}
#include "tinylang/Basic/Diagnostic.h"

using namespace tinylang;

namespace {
const char *DiagnosticText[] = {
    #define DIAG(ID, Level, Msg) Msg,
    #include "tinylang/Basic/Diagnostic.def"
};
\end{cpp}

与头文件中一样，定义DIAG宏来检索所需的部件。这里，定义了一个保存文本消息的数组。因此，DIAG宏只返回Msg部分。我们对于level中使用了相同的方法:

\begin{cpp}
SourceMgr::DiagKind DiagnosticKind[] = {
#define DIAG(ID, Level, Msg) SourceMgr::DK_##Level,
include "tinylang/Basic/Diagnostic.def"
};
} // namespace
\end{cpp}

毫不奇怪，这两个函数都只是对数组进行索引，并返回所需的数据:

\begin{cpp}
const char *
DiagnosticsEngine::getDiagnosticText(unsigned DiagID) {
    return DiagnosticText[DiagID];
}

SourceMgr::DiagKind
DiagnosticsEngine::getDiagnosticKind(unsigned DiagID) {
    return DiagnosticKind[DiagID];
}
\end{cpp}

SourceMgr和DiagnosticsEngine类的组合为其他组件提供了良好的基础。我们将首先在lexer中使用它们!
